﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using NLite.Collections;

namespace NLite.Internal
{
    class PropertySetCaretaker 
    {
        public string FileName { get; set; }

        

        public IPropertySet Get()
        {
            return PropertySetHelper.Load(FileName);
        }

        public void Save(IPropertySet memento)
        {
            PropertySetHelper.Save(memento, FileName);
        }

        #region Helper method

        private class PropertySetHelper
        {
            internal static PropertySet ReadFromAttributes(XmlReader reader)
            {
                PropertySet properties = new PropertySet();
                if (reader.HasAttributes)
                {
                    for (int i = 0; i < reader.AttributeCount; i++)
                    {
                        reader.MoveToAttribute(i);
                        properties[reader.Name] = reader.Value;
                    }
                    reader.MoveToElement(); //Moves the p back to the element element.
                }
                return properties;
            }

            internal static IPropertySet ReadProperties(XmlReader reader, string endElement)
            {
                var memento = new PropertySet();
                if (reader.IsEmptyElement)
                    return memento;

                while (reader.Read())
                {
                    switch (reader.NodeType)
                    {
                        case XmlNodeType.EndElement:
                            if (reader.LocalName == endElement)
                                return memento;
                            break;
                        case XmlNodeType.Element:
                            string propertyName = reader.LocalName;
                            if (propertyName == "Properties")
                            {
                                propertyName = reader.GetAttribute(0);
                                memento[propertyName] = ReadProperties(reader, "Properties");
                            }
                            else if (propertyName == "Array")
                            {
                                propertyName = reader.GetAttribute(0);
                                memento[propertyName] = ReadArray(reader);
                            }
                            else if (propertyName == "SerializedValue")
                            {
                                propertyName = reader.GetAttribute(0);
                                memento[propertyName] = new SerializedValue(reader.ReadInnerXml());
                            }
                            else
                            {
                                memento[propertyName] = reader.HasAttributes ? reader.GetAttribute(0) : null;
                            }
                            break;
                    }
                }

                return memento;
            }

            internal static List<string> ReadArray(XmlReader reader)
            {
                if (reader.IsEmptyElement)
                    return new List<string>(0);
                List<string> l = new List<string>();
                while (reader.Read())
                {
                    switch (reader.NodeType)
                    {
                        case XmlNodeType.EndElement:
                            if (reader.LocalName == "Array")
                                return l;
                            break;
                        case XmlNodeType.Element:
                            l.Add(reader.HasAttributes ? reader.GetAttribute(0) : null);
                            break;
                    }
                }
                return l;
            }

            internal static void WriteProperties(XmlWriter writer, IEnumerable<KeyValuePair<string, object>> properties)
            {
                lock (properties)
                {
                    List<KeyValuePair<string, object>> sortedProperties = new List<KeyValuePair<string, object>>(properties);
                    sortedProperties.Sort((a, b) => StringComparer.OrdinalIgnoreCase.Compare(a.Key, b.Key));

                    foreach (KeyValuePair<string, object> entry in sortedProperties)
                    {
                        object val = entry.Value;
                        if (TypeDescriptor.GetConverter(val).CanConvertFrom(typeof(string)))
                        {
                            writer.WriteStartElement(entry.Key);
                            WriteValue(writer, val);
                            writer.WriteEndElement();
                            continue;
                        }

                        var dic = val as IDictionary<string, object>;
                        if (dic != null)
                        {
                            writer.WriteStartElement("Properties");
                            writer.WriteAttributeString("name", entry.Key);
                            WriteProperties(writer, dic);
                            writer.WriteEndElement();
                            continue;
                        }

                        var array = val as IEnumerable;
                        if (array != null)
                        {
                            writer.WriteStartElement("Array");
                            writer.WriteAttributeString("name", entry.Key);
                            foreach (object o in array)
                            {
                                writer.WriteStartElement("Element");
                                WriteValue(writer, o);
                                writer.WriteEndElement();
                            }
                            writer.WriteEndElement();
                            continue;
                        }

                        var svalue = val as SerializedValue;
                        if (svalue != null)
                        {
                            writer.WriteStartElement("SerializedValue");
                            writer.WriteAttributeString("name", entry.Key);
                            writer.WriteRaw(svalue.Content);
                            writer.WriteEndElement();
                        }
                        else
                        {
                            writer.WriteStartElement("SerializedValue");
                            writer.WriteAttributeString("name", entry.Key);
                            XmlSerializer serializer = new XmlSerializer(val.GetType());
                            serializer.Serialize(writer, val, null);
                            writer.WriteEndElement();
                        }
                    }
                }
            }

            internal static void WriteValue(XmlWriter writer, object val)
            {
                if (val != null)
                {
                    if (val is string)
                    {
                        writer.WriteAttributeString("value", val.ToString());
                    }
                    else
                    {
                        TypeConverter c = TypeDescriptor.GetConverter(val.GetType());
                        writer.WriteAttributeString("value", c.ConvertToInvariantString(val));
                    }
                }
            }

            internal static void Save(IEnumerable<KeyValuePair<string, object>> properties, string fileName)
            {
                using (XmlTextWriter writer = new XmlTextWriter(fileName, Encoding.UTF8))
                {
                    writer.Formatting = Formatting.Indented;
                    writer.WriteStartElement("Properties");
                    WriteProperties(writer, properties);
                    writer.WriteEndElement();
                }
            }

            internal static IPropertySet Load(string fileName)
            {
                if (!File.Exists(fileName))
                    return new PropertySet();
                using (XmlTextReader reader = new XmlTextReader(fileName))
                {
                    while (reader.Read())
                    {
                        if (reader.IsStartElement())
                        {
                            switch (reader.LocalName)
                            {
                                case "Properties":
                                    return ReadProperties(reader, "Properties");
                            }
                        }
                    }
                }

                return new PropertySet();
            }
        }

        #endregion
    }
}
